iT邦幫忙

2026 iThome 鐵人賽

DAY 21
0
Modern Web

WebMCP:30 天打造 AI Agent 看得懂、也操作得動的網站系列 第 21 篇

Day 21|Agent 會操作網站後,最危險的不是幻覺:Origin、權限與最小能力

  • 分享至 

  • xImage
  •  

本篇重點

WebMCP 的工具存取、網站登入與業務權限,是三個分開處理的問題。瀏覽器決定文件能否使用或存取 Tool,後端識別目前使用者,再檢查這位使用者是否有權執行操作。

今天用公開商品頁、會員訂單頁與管理員編輯頁示範:工具清單隨身分切換,而每次 API 請求仍由後端驗證。Member 可以查自己的訂單;換成其他人的訂單編號,後端就回傳 HTTP 403。

先分三層權限

我會把安全分成:

Layer 1:Browser / Origin
誰的 Document 可以存取 Tool?

Layer 2:Application Session
目前使用者是誰?

Layer 3:Business Authorization
這個使用者能不能做這件事?

工具出現在清單裡,只表示目前頁面提供這項功能。能取得哪些資料、能修改哪些內容,還要通過後端的身分與權限檢查。

圖片 1|Origin、Session 與業務權限三層邊界
Origin、Session 與業務權限三層邊界

Layer 1:WebMCP 的 Origin 邊界

WebMCP Draft 目前把 API 整合進 tools Permissions Policy,default allowlist 是:

'self'

Origin 由協定、主機與連接埠組成,例如 localhost:8080 與 localhost:8081 是不同 Origin。tools Permissions Policy 控制文件能否使用 WebMCP API;跨 Origin iframe 需要另外處理政策允許條件。

同時,Tool 註冊還能指定:

await document.modelContext.registerTool(tool, {
  exposedTo: ['https://trusted.example']
});

exposedTo 指定文件樹內可存取該 Tool 的跨 Origin 對象;它與 API 的 Permissions Policy 是不同檢查,也不會授予會員或管理員權限。本日示範回應 Permissions-Policy: tools=(self),不設定 exposedTo;iframe 操作接續 Day 22。

Layer 2:使用者 Session

網站使用 Cookie Session 時,Tool 透過同 Origin API 請求帶上目前的 Cookie,由後端識別使用者。WebMCP 沿用網站的登入狀態,不會另外建立一個具有額外權限的 Agent 帳號。

本例切換為 Member 後,後端 Session 對應 User 123;未登入時查詢訂單,API 回傳 HTTP 401 與 unauthenticated。身分由 Session 取得,不由 Tool 的 Arguments 指定。

Layer 3:Authorization

登入成功後,後端還要核對資料所有權。本例訂單 1234 屬於 User 123,訂單 999 屬於另一位示範會員。

收到 GET /api/orders/999 時,後端依 Session 取得使用者,再比較訂單的擁有者:

if order['ownerId'] != session['userId']:
    return self.fail(403, 'forbidden')

因此,單純更換訂單編號不會取得其他會員的資料。在 WordPress 中,管理操作同樣要搭配 current_user_can(...) 檢查能力,並依資料類型核對所有權。

Read-only Tool 也可能洩漏資料

例如:

get_favorite_products
get_order_history
get_saved_addresses

它們 readOnlyHint: true,但結果可能非常私人。

所以:

readOnly
≠ public
≠ safe to expose cross-origin

Chrome Security Guidance 也特別提醒,read-only Tool 仍可能透露 User Data。

最小能力原則

工具清單依目前頁面需要的功能縮小,不把所有管理功能註冊到每個頁面。本例切換身分時,也切換對應的頁面情境:

  • Guest/公開商品頁:search_products、get_product。
  • Member/會員訂單頁:get_orders、get_order。
  • Admin/管理員編輯頁:save_draft、prepare_publish。

每次切換都先移除上一組工具,再註冊新工具。Admin 編輯頁只提供草稿與預覽功能,不累加商品或訂單工具。

開啟本地測試頁

  1. 在專案目錄的終端機執行 python day21_server.py,保持服務運行。
  2. 開啟 http://localhost:8081/,確認標題為「Day 21 - Origin、Session 與權限」。
  3. 開啟這個分頁的 WebMCP Inspector。以下使用 Execute Tool,逐一核對工具與後端回應。

本日需要 Python API 後端;靜態檔案伺服器不提供 Session 與權限檢查。角色按鈕是本地教學用登入入口,商品、訂單與草稿皆為示範資料。正式網站的登入入口需接上自己的帳號驗證。

先查看 Guest 的工具

  1. 確認目前身分為 Guest;若已登入,按「登出為 Guest」。
  2. 查看頁面與 Inspector 的 WebMCP Tools,兩邊都列出 search_products、get_product。

圖片 2|Guest 的公開商品工具
Guest 的商品工具清單

切換 Member,查看訂單工具

  1. 按頁面「切換為 Member」。
  2. 確認身分顯示 Member/會員訂單頁(User 123)。
  3. Inspector 的工具清單更新為 get_orders、get_order。

圖片 3|Member 的會員訂單工具
Member 的訂單工具清單

查詢本人訂單

  1. 保持 Member,在 Inspector 的 Tool 選 get_order。
  2. 在 Input Arguments 輸入:
{"orderId":1234}
  1. 按 Execute Tool,結果回傳 httpStatus: 200、status: "success"。
  2. 核對訂單編號為 1234、ownerId 為 123,與目前登入會員相符。

圖片 4|查詢本人訂單,回傳 HTTP 200
查詢本人訂單 1234 成功

改查其他人的訂單

  1. 保持同一位 Member 與同一個 get_order Tool。
  2. 將 Input Arguments 改為:
{"orderId":999}
  1. 再按 Execute Tool,結果回傳 httpStatus: 403、status: "forbidden"。
  2. 頁面的「最後一次請求」同步顯示 /api/orders/999 與 403,回傳內容沒有訂單明細。

圖片 5|查詢非本人訂單,回傳 HTTP 403
查詢非本人訂單 999 遭拒

這兩次呼叫使用相同的 Tool,差別只有訂單編號。結果顯示:讀取工具仍會核對資料所有權,工具可見性不會取代後端授權。

最後切換 Admin,查看編輯工具

  1. 按「切換為 Admin」。
  2. 確認身分顯示 Admin/管理員編輯頁(User 1)。
  3. Inspector 的工具清單更新為 save_draft、prepare_publish。

圖片 6|Admin 的草稿與預覽工具
Admin 的草稿與預覽工具清單

save_draft 保存本地草稿,prepare_publish 產生草稿預覽,兩者都不會發布文章。

完成後按「下載紀錄 JSON」,保存這次 API 請求紀錄,再按「登出為 Guest」。登出完成訊息會保留在頁面,工具清單恢復為商品搜尋與讀取功能。重新整理會清除本頁顯示的請求紀錄;切換身分、登出或重啟服務會清除該 Session 的草稿。

Cross-origin exposure 要像 CORS 一樣保守

不要:

exposedTo: ['*']

目前 API 本身就要求具體安全 origins 的思路;即使未來能力改變,也不應把使用者資料 Tool 廣泛公開。

我會像設計 CORS/OAuth redirect URI 一樣:

誰真的需要?
為什麼需要?
可以只開 read-only 嗎?
資料是否含個資?
是否有稽核紀錄?

Tool Description 不是安全限制

Description 用來說明工具的使用情境,例如:

Only admins should call this tool.

這段文字能提示 Agent,但實際權限由後端程式檢查。以下是本例保存草稿 API 的前端呼叫方式,input 包含 title 與 body:

async function saveDraft(input) {
  const sessionResponse = await fetch('/api/session', {
    credentials: 'same-origin'
  });
  const session = await sessionResponse.json();
  const response = await fetch('/api/admin/draft', {
    method: 'POST',
    credentials: 'same-origin',
    headers: {
      'Content-Type': 'application/json',
      'X-CSRF-Token': session.csrf ?? ''
    },
    body: JSON.stringify(input)
  });
  const result = await response.json();
  return { httpStatus: response.status, ...result };
}

後端依序檢查請求 Origin、Session、CSRF token 與管理員角色,再處理草稿。未登入回傳 401;已登入但不具管理員權限回傳 403。即使從工具清單以外直接呼叫 API,也會經過同一套檢查。

我的 WebMCP Security Checklist

[ ] Tool 是否只在需要的頁面/狀態存在?
[ ] 是否正確標 readOnly / consequential / untrusted?
[ ] 是否含私人資料?
[ ] 是否需要登入?
[ ] Server 是否重新驗證 Authorization?
[ ] 是否真的需要 exposedTo?
[ ] Cross-origin allowlist 是否最小化?
[ ] 高風險 Action 是否有 explicit confirmation?
[ ] Result 是否避免敏感內部資訊?

可帶走的重點

  1. Origin、Session 與業務權限分開檢查,各自處理工具存取、登入身分與操作授權。
  2. exposedTo 指定跨 Origin 的工具暴露對象,不會授予資料存取權限。
  3. 本例 Guest、Member、Admin 的頁面各提供兩個對應工具。
  4. Member 查本人訂單 1234 成功;改查訂單 999,後端回傳 HTTP 403。
  5. Read-only 只表示不修改資料,私人資料仍須驗證權限。
  6. Description 說明使用情境,後端負責執行權限檢查。

參考資料


上一篇
Day 20|網站上的一句話就能騙 Agent?Prompt Injection 與 WebMCP 的安全邊界
下一篇
Day 22|Iframe 裡的 Tool 誰說了算?跨 Origin WebMCP 權限實驗
系列文
WebMCP:30 天打造 AI Agent 看得懂、也操作得動的網站 共 22 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言